package com.yuncheng.shiro.authc;

import com.yuncheng.common.api.AuthenticationAPI;
import com.yuncheng.rest.Oauth2RestClient;
import com.yuncheng.system.api.SystemAPI;
import com.yuncheng.constant.CommonConstant;
import com.yuncheng.shiro.utils.RequestUtils;
import com.yuncheng.utils.*;
import com.yuncheng.vo.CurrentUserVo;
import com.yuncheng.vo.LoginUser;
import com.yuncheng.vo.SysYmlProperties;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Lazy;
import org.apache.commons.lang3.StringUtils;

import javax.annotation.Resource;
import java.util.Map;
import java.util.Set;

/**
 * 用户登录鉴权和获取用户授权
 */
public class ShiroRealm extends AuthorizingRealm {

    private static final Logger log = LoggerFactory.getLogger(ShiroRealm.class);

    @Resource
    @Lazy
    private SystemAPI sysBaseAPI;
    @Resource
    @Lazy
    private RedisUtil redisUtil;
    @Resource
    @Lazy
    private SysYmlProperties properties;
    @Resource
    @Lazy
    private Oauth2RestClient oauth2RestClient;

    /**
     * 必须重写此方法，不然Shiro会报错
     */
    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof JwtToken;
    }

    /**
     * 权限信息认证(包括角色以及权限)是用户访问controller的时候才进行验证(redis存储的此处权限信息)
     * 触发检测用户权限时才会调用此方法，例如checkRole,checkPermission
     *
     * @param principals 身份信息
     * @return AuthorizationInfo 权限信息
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        log.debug("===============Shiro权限认证开始============ [ roles、permissions]==========");
        String username = null;
        if (principals != null) {
            LoginUser sysUser = (LoginUser) principals.getPrimaryPrincipal();
            username = sysUser.getUsername();
        }
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();

        // 设置用户拥有的角色集合，比如“admin,test”
        Set<String> roleSet = sysBaseAPI.getUserRolesSet(username);
        info.setRoles(roleSet);

        // 设置用户拥有的权限集合，比如“system:role:add,system:user:add”
        Set<String> permissionSet = sysBaseAPI.getUserPermissionsSet(username);
        info.addStringPermissions(permissionSet);
        log.debug("===============Shiro权限认证成功==============");
        return info;
    }

    /**
     * 用户信息认证是在用户进行登录的时候进行验证(不存redis)
     * 也就是说验证用户输入的账号和密码是否正确，错误抛出异常
     *
     * @param auth 用户登录的账号密码信息
     * @return 返回封装了用户信息的 AuthenticationInfo 实例
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth) throws AuthenticationException {
        String token = (String) auth.getCredentials();
        if (token == null) {
            log.debug("———身份认证失败——IP地址:  " + RequestUtils.getIpAddrByRequest(SpringContextUtils.getHttpServletRequest()));
            throw new AuthenticationException(I18nUtil.message("token.is.null"));
        }
        ThreadContextHelper.setToken(token);
        // 校验token有效性
        LoginUser loginUser = this.checkUserTokenIsEffect(token);

        CurrentUserVo currentUser = new CurrentUserVo();
        currentUser.setId(loginUser.getId());
//		currentUser.setCode(loginUser.getUsername());
        currentUser.setName(loginUser.getRealname());
        currentUser.setLoginName(loginUser.getUsername());
        currentUser.setDeptId(loginUser.getDeptId());
		currentUser.setDeptCode(loginUser.getDeptCode());
        currentUser.setDeptName(loginUser.getDeptName());
        currentUser.setOrgId(loginUser.getOrgId());
        currentUser.setOrgCode(loginUser.getOrgCode());
        currentUser.setOrgName(loginUser.getOrgName());
        Map<String, AuthenticationAPI> authenticationAPIMap = SpringContextUtils.getApplicationContext().getBeansOfType(AuthenticationAPI.class);
        authenticationAPIMap.forEach((k, v) -> v.setAuthUser(currentUser));
        ThreadContextHelper.setCurrentUser(currentUser);
        return new SimpleAuthenticationInfo(loginUser, token, getName());
    }

    /**
     * 校验token的有效性
     *
     * @param token
     */
    public LoginUser checkUserTokenIsEffect(String token) throws AuthenticationException {
        // 解密获得username，用于和数据库进行对比
        String username = JwtUtil.getUsername(token);
        if (username == null) {
            throw new AuthenticationException(I18nUtil.message("token.is.invalid"));
        }

        // 查询用户信息
        log.debug("———校验token是否有效————checkUserTokenIsEffect——————— " + token);
        LoginUser loginUser = sysBaseAPI.getUserByName(username);
        if (loginUser == null) {
            throw new AuthenticationException(I18nUtil.message("user.not.exists"));
        }
        // 判断用户状态
        if (loginUser.getStatus() != 1) {
            throw new AuthenticationException(I18nUtil.message("user.blocked"));
        }
        // 校验token是否超时失效 & 或者账号密码是否错误
        if (!jwtTokenRefresh(token, username, loginUser.getPassword())) {
            throw new AuthenticationException(I18nUtil.message("token.is.invalid"));
        }
        // 初始化最小库之后，需要重新登录
        if (StringUtils.isEmpty(loginUser.getDeptId())) {
            throw new AuthenticationException(I18nUtil.message("token.is.invalid"));
        }

        return loginUser;
    }

    /**
     * JWTToken刷新生命周期 （实现： 用户在线操作不掉线功能）
     * 1、登录成功后将用户的JWT生成的Token作为k、v存储到cache缓存里面(这时候k、v值一样)，缓存有效期设置为Jwt有效时间的2倍
     * 2、当该用户再次请求时，通过JWTFilter层层校验之后会进入到doGetAuthenticationInfo进行身份验证
     * 3、当该用户这次请求jwt生成的token值已经超时，但该token对应cache中的k还是存在，则表示该用户一直在操作只是JWT的token失效了，程序会给token对应的k映射的v值重新生成JWTToken并覆盖v值，该缓存生命周期重新计算
     * 4、当该用户这次请求jwt在生成的token值已经超时，并在cache中不存在对应的k，则表示该用户账户空闲超时，返回用户信息已失效，请重新登录。
     * 注意： 前端请求Header中设置Authorization保持不变，校验有效性以缓存中的token为准。
     * 用户过期时间 = Jwt有效时间 * 2。
     *
     * @param userName
     * @param passWord
     * @return
     */
    public boolean jwtTokenRefresh(String token, String userName, String passWord) {
        String cacheToken = String.valueOf(redisUtil.get(CommonConstant.PREFIX_USER_TOKEN + token));
        if (cacheToken != null && !cacheToken.equals("") && !cacheToken.equals("null")) {
            // 校验token有效性
            if (!JwtUtil.verify(cacheToken, userName, passWord)) {
                String newAuthorization = JwtUtil.sign(userName, passWord);
                // 设置超时时间
                redisUtil.set(CommonConstant.PREFIX_USER_TOKEN + token, newAuthorization);
                redisUtil.expire(CommonConstant.PREFIX_USER_TOKEN + token, properties.getJwtTime() * 2 / 1000);

                if (redisUtil.hasKey(CommonConstant.PREFIX_USER_TOKEN + token + CommonConstant.OAUTH2_TOKEN + Oauth2RestClient.DEFAULT_CLIENT_NAME)) {
                    redisUtil.expire(CommonConstant.PREFIX_USER_TOKEN + token + CommonConstant.OAUTH2_TOKEN + Oauth2RestClient.DEFAULT_CLIENT_NAME, properties.getJwtTime() * 2 / 1000);
                }

                log.debug("——————————用户在线操作，更新token保证不掉线—————————jwtTokenRefresh——————— " + token);
            }
            return true;
        }
        return false;
    }

    /**
     * 清除当前用户的权限认证缓存
     *
     * @param principals 权限信息
     */
    @Override
    public void clearCache(PrincipalCollection principals) {
        super.clearCache(principals);
    }

}
